// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include <string>
#include <memory>
#include <sstream>

struct curl_slist;

namespace static_curl {

class FCurlRequest;
class FCurlResponse;

/**
 * Base class for the lambda.
 */
class FCurlExecutorBase
{
public:
	virtual void Invoke(FCurlResponse& res) = 0;
};

/**
 * Hold the copy of lambda body
 */
template<typename Body>
class FCurlExecutor : public FCurlExecutorBase
{
	Body	_body;

public:
	FCurlExecutor(Body body)
		: _body(body)
	{}

	void Invoke(FCurlResponse& res) override
	{
		_body(res); // Delegate
	}
};

/**
 * Thin wrapper for libcurl responses.
 */
class FCurlResponse
{
public:
	FCurlResponse(void* curl);
	~FCurlResponse();

	int Code() const;
	std::string Loc() const;
	const std::string& ContentType() const;

	//json Json() const;
	std::string PlainText() const;

	bool IsCurlError() const;
	const std::string& CurlError() const;
	std::string ErrorMessage() const;

private:
	friend class FCurlRequest;
	void* _curl;
	int _code;
	std::string _location;
	std::string _contentType;
	std::string _errstr;
	std::stringstream _stringOut;
};

/**
 * Thin wrapper for libcurl requests.
 */
class FCurlRequest
{
public:

	FCurlRequest();
	~FCurlRequest();

	FCurlRequest& AcceptEnc(const char* coding);
	FCurlRequest& FollowLoc(long follow_loc);

	FCurlRequest& Put(const std::string& endpoint, std::ifstream* infile);

	FCurlRequest& Get(const std::string& endpoint);
	FCurlRequest& Get(const std::string& schemehostport, const std::string& path);

	FCurlRequest& Post(const std::string& endpoint);
	FCurlRequest& Post(const std::string& schemehostport, const std::string& path);

	FCurlRequest& Then(std::shared_ptr<FCurlExecutorBase>&& body);
	FCurlRequest& Then();

	FCurlRequest& Catch(std::shared_ptr<FCurlExecutorBase>&& body);

	FCurlRequest& Query(const std::string& name, const std::string& value);
	FCurlRequest& Query();

	FCurlRequest& Stream(std::ostream& strm);
	//FCurlRequest& Json(const json& jsonObj);

	FCurlRequest& Debug(std::ostream& strm);

	FCurlResponse& Perform();

public:
	template<typename Body>
	FCurlRequest& Catch(Body body)
	{
		std::shared_ptr<FCurlExecutorBase> base =
			std::make_shared<FCurlExecutor<Body>>(body);
		return Catch(std::move(base));
	}

	template<typename Body>
	FCurlRequest& Then(Body body)
	{
		std::shared_ptr<FCurlExecutorBase> base =
			std::make_shared<FCurlExecutor<Body>>(body);
		return Then(std::move(base));
	}

	template<typename T>
	FCurlRequest& Query(const std::string& name, const T& value)
	{
		return Query(name, std::to_string(value));
	}

private:
	enum class Method { GET, POST, PUT };
	void* _curl;
	curl_slist* _headers;
	Method _method;
	std::string _endpoint;
	std::string _querystring;
	std::shared_ptr<FCurlExecutorBase> _then;
	std::shared_ptr<FCurlExecutorBase> _catch;
	FCurlResponse _response;
};

} // namespace static_curl


class UtilHDFSFile
{
public:
	static bool downloadHDFSFileTo(
		const std::string& url,
		const std::string& dstFile);
	static bool uploadHDFSFileTo(
		const std::string& srcFile,
		const std::string& url);
};